import VirtualList from '../virtual-list';
import Node from './Node';
import type { TreeCheckMod, TreeNode} from './store';
import { TreeStore } from './store';
import Dropdown from '../Dropdown';
import { defineComponent, PropType, provide, ref, VNode, watch, watchEffect } from 'vue';

export * from './store';

export type NodeKeyType = string | number

export interface TreeContextProps {
    onOpenClose: (node: any) => void
    onNodeSelect: (node: any) => void
    store: any, // 存储树状结构的数据
    draggable?: boolean
    checkable: boolean
    directory?: boolean
    contextMenu?: VNode
    selectedClass?: string;
    dragHoverClass?: string;
    draggingClass?: string;
    onContextMenu?: (data: any) => void
    onDragStart?: (e: any, node: any) => void
    onDragEnter?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onDragOver?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onDragLeave?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onDrop: (e: any, targetKey: NodeKeyType, dropPosition: dragHoverPartEnum) => void
    onNodeCheck: (node: NodeKeyType | TreeNode, checked: boolean) => void
    customIcon?: (node: TreeNode) => VNode;
    arrowIcon?: () => VNode;
}

export interface TreeInstanceProps {
    prepend: (parentKey: NodeKeyType, nodeId: NodeKeyType|TreeNode) => void
    append: (parentKey: NodeKeyType, nodeId: NodeKeyType|TreeNode) => void
    insertBefore: (targetKey: NodeKeyType, nodeId: NodeKeyType|TreeNode) => void
    insertAfter: (targetKey: NodeKeyType, nodeId: NodeKeyType|TreeNode) => void
    getNode: (nodeId: NodeKeyType) => TreeNode
    remove: (nodeId: NodeKeyType|TreeNode) => void
    filter: (keyword: string, filterMethod: any) => void
    expandAll: () => void
    collapseAll: () => void
    expandNode: (nodeId: TreeNode | NodeKeyType, expand: boolean) => Promise<void>
    scrollTo: (nodeId: TreeNode | NodeKeyType, position : 'top'|'center'|'bottom') => void
    rename: (nodeId: TreeNode | NodeKeyType, title: string) => void
    checkNode: (node: TreeNode | NodeKeyType, checked: boolean) => void
    checkAll: () => void
    uncheckAll: () => void
    loadData: (nodeId: TreeNode | NodeKeyType, loadDataMethod: (node: TreeNode) => Promise<TreeNode[]>) => void
    selectNode: (nodeId: NodeKeyType | TreeNode, silence?: boolean) => void
    getChecked: (mode: TreeCheckMod) => TreeNode[]
    getCheckedKeys: (mode: TreeCheckMod) => NodeKeyType[]
}

export interface TreeProps {
    emptyText?: string;
    data: TreeNode[];
    checkable?: boolean;
    checkRelation?: 'related' | 'unRelated';
    directory?: boolean;
    contextMenu?: VNode;
    onContextMenu?: (data: TreeNode) => void;
    onNodeCheck?: (node: NodeKeyType | TreeNode, checked: boolean) => void;
    onNodeSelect?: (node: TreeNode) => void;
    onOpenClose?: (node: NodeKeyType | TreeNode) => void;
    ref?: any;
    draggable?: boolean;
    style?: any;
    class?: string;
    classList?: any;
    loadData?: (node: NodeKeyType | TreeNode) => Promise<any>;
    beforeDropMethod?: (node: TreeNode, dragNode: TreeNode, hoverPart: dragHoverPartEnum) => Promise<boolean>;
    beforeExpand?: (node: TreeNode, expand: boolean) => Promise<boolean>;
    onNodeDrop?: (e: any, node: TreeNode, dragNode: TreeNode, hoverPart: dragHoverPartEnum) => void;
    onNodeDragStart?: (e: any, node: TreeNode) => void;
    onNodeDragEnter?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onNodeDragOver?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onNodeDragLeave?: (e: any, node: any, hoverPart: dragHoverPartEnum) => void
    onSelectMenu?: (name: string) => void;
    onNodeExpand?: (node: TreeNode) => void;
    onNodeCollapse?: (node: TreeNode) => void;
    onChange?: (value: NodeKeyType[]) => void;
    selected?: NodeKeyType;
    modelValue?: NodeKeyType[];
    keyField?: string;
    titleField?: string;
    selectedClass?: string;
    dragHoverClass?: string;
    draggingClass?: string;
    customIcon?: (node: TreeNode) => VNode;
    arrowIcon?: () => VNode;
    mode?: TreeCheckMod;
}

export enum dragHoverPartEnum {
    before = 'before',
    body = 'body',
    after = 'after'
}

export const TreeContextKey = Symbol('TreeContextKey');

export default defineComponent({
    name: 'Tree',
    props: {
        emptyText: {type: String as PropType<TreeProps['emptyText']>, default: '暂无数据'},
        data: {type: Array as PropType<TreeProps['data']>, default: () => []},
        checkable: {type: Boolean as PropType<TreeProps['checkable']>, default: false},
        checkRelation: {type: String as PropType<TreeProps['checkRelation']>, default: 'related'},
        directory: {type: Boolean as PropType<TreeProps['directory']>, default: false},
        contextMenu: {type: Object as PropType<TreeProps['contextMenu']>},
        draggable: {type: Boolean as PropType<TreeProps['draggable']>, default: false},
        loadData: {type: Function as PropType<TreeProps['loadData']>},
        beforeDropMethod: {type: Function as PropType<TreeProps['beforeDropMethod']>},
        beforeExpand: {type: Function as PropType<TreeProps['beforeExpand']>},
        onNodeDragStart: {type: Function as PropType<TreeProps['onNodeDragStart']>},
        onNodeDragEnter: {type: Function as PropType<TreeProps['onNodeDragEnter']>},
        onNodeDragOver: {type: Function as PropType<TreeProps['onNodeDragOver']>},
        onNodeDragLeave: {type: Function as PropType<TreeProps['onNodeDragLeave']>},
        onSelectMenu: {type: Function as PropType<TreeProps['onSelectMenu']>},
        onChange: {type: Function as PropType<TreeProps['onChange']>},
        onContextMenu: {type: Function as PropType<TreeProps['onContextMenu']>},
        selected: {type: [String, Number] as PropType<TreeProps['selected']>},
        modelValue: {type: Array as PropType<TreeProps['modelValue']>},
        keyField: {type: String as PropType<TreeProps['keyField']>},
        titleField: {type: String as PropType<TreeProps['titleField']>},
        selectedClass: {type: String as PropType<TreeProps['selectedClass']>},
        draggingClass: {type: String as PropType<TreeProps['draggingClass']>},
        customIcon: {type: Function as PropType<TreeProps['customIcon']>},
        arrowIcon: {type: Function as PropType<TreeProps['arrowIcon']>},
        mode: {type: String as PropType<TreeProps['mode']>},
    },
    emits: ['nodeSelect', 'nodeExpand', 'nodeCollapse', 'nodeChecked', 'change',
        'update:modelValue', 'update:selected', 'nodeDrop'],
    setup (props: TreeProps, {emit, expose}) {
        const vir = ref();
        const visible = ref<boolean>(false);
        const x = ref<number>(0);
        const y = ref<number>(0);
        const emptyText = props.emptyText ?? '暂无数据';
        const treeStore = new TreeStore(props, emit);
        const store = treeStore.getStore();

        watch(() => props.data, () => {
            treeStore.init(props.data);
        }, { immediate: true });

        const onNodeCheck = (node: TreeNode | NodeKeyType, checked: boolean) => {
            treeStore.checkNode(node, checked);
            emit('nodeChecked', node, checked);
        };

        const onNodeExpandCollapse = async (node: TreeNode) => {
            const valid = props.beforeExpand ? await props.beforeExpand(node, !node.expand) : true;
            if (!valid) {
                return;
            }
            if (node.loading && props.loadData) {
                await treeStore.loadData(node, props.loadData);
            }
            treeStore.expandNode(node, !node.expand);
        };

        expose({
            prepend: treeStore.prepend,
            append: treeStore.append,
            insertBefore: treeStore.insertBefore,
            insertAfter: treeStore.insertAfter,
            getNode: treeStore.getNode,
            remove: treeStore.remove,
            filter: treeStore.filter,
            expandAll: treeStore.expandAll,
            collapseAll: treeStore.collapseAll,
            expandNode: async (nodeId: TreeNode | NodeKeyType, expand: boolean) => {
                const node = treeStore._getNode(nodeId);
                if (!node) return;
                if (node.expand !== expand) {
                    onNodeExpandCollapse(node);
                }
            },
            scrollTo: (nodeId: TreeNode | NodeKeyType, position : 'top'|'center'|'bottom' = 'top') => {
                const index = treeStore.getNodeIndexInShow(nodeId);
                const nodeHeight = 22;
                let top = index * nodeHeight;
                console.log(vir.value);

                if (position === 'center') {
                    top = top - vir.value.$el.getBoundingClientRect().height / 2 + nodeHeight / 2;
                }
                if (position === 'bottom') {
                    top = top - vir.value.$el.getBoundingClientRect().height + nodeHeight;
                }
                vir.value.$el.scrollTo({top, behavior: 'smooth'});
            },
            rename: treeStore.rename,
            checkNode: onNodeCheck,
            checkAll: treeStore.checkAll,
            uncheckAll: treeStore.uncheckAll,
            loadData: (nodeId: TreeNode | NodeKeyType, loadDataMethod: (node: TreeNode) => Promise<TreeNode[]>) => {
                const node = treeStore._getNode(nodeId);
                if (!node) return;
                treeStore.loadData(node, loadDataMethod);
            },
            selectNode: treeStore.selectNode,
            getChecked: treeStore.getChecked,
            getCheckedKeys: treeStore.getCheckedKeys,
        });

        const handleDrop = async (e: any, targetKey: NodeKeyType, hoverPart: dragHoverPartEnum) => {
            if (e.dataTransfer) {
                try {
                    const node: TreeNode = treeStore._getNode(targetKey);
                    const dragNodeKey = e.dataTransfer.getData('node');
                    const dragNode: TreeNode = treeStore._getNode(dragNodeKey);
                    const shouldDrop: boolean = props.beforeDropMethod ? await props.beforeDropMethod(
                        node,
                        dragNode,
                        hoverPart
                    ) : true;

                    if (shouldDrop) {
                        if (targetKey === dragNodeKey) return;
                        if (hoverPart === dragHoverPartEnum.before) {
                            treeStore.insertBefore(targetKey, dragNodeKey);
                        // 如果是拖拽到父节点，并且父节点是展开的，则不管 hoverPart 是不是 after 都拖入为子节点
                        } else if (hoverPart === dragHoverPartEnum.body || node.expand) {
                            treeStore.prepend(targetKey, dragNodeKey);
                        } else if (hoverPart === dragHoverPartEnum.after) {
                            treeStore.insertAfter(targetKey, dragNodeKey);
                        }
                        emit('nodeDrop', e, node, dragNode, hoverPart);
                    }
                } catch (err: any) {
                    throw new Error(err);
                }
            }
        };

        provide(TreeContextKey, {
            onOpenClose: onNodeExpandCollapse, onNodeSelect: treeStore.selectNode,
            store: treeStore, draggable: props.draggable, checkable: props.checkable || false, onDrop: handleDrop,
            directory: props.directory, onContextMenu: props.onContextMenu, contextMenu: props.contextMenu,
            onNodeCheck: onNodeCheck, onDragStart: props.onNodeDragStart, onDragEnter: props.onNodeDragEnter,
            onDragLeave: props.onNodeDragLeave, onDragOver: props.onNodeDragOver, selectedClass: props.selectedClass,
            dragHoverClass: props.dragHoverClass, draggingClass: props.draggingClass, customIcon: props.customIcon,
            arrowIcon: props.arrowIcon
        });

        return () => props.contextMenu
            ? props.data && props.data.length
                ? <Dropdown v-model={visible.value} transfer trigger="contextMenu" position={{x: x.value, y: y.value}}
                    onMouseClick={(e) => {
                        x.value = e.pageX;
                        y.value = e.pageY + 5;
                    }}
                    handler=".cm-tree-text" align="bottomLeft" menu={props.contextMenu} onSelect={props.onSelectMenu}>
                    <div class="cm-tree">
                        <VirtualList onScroll={() => visible.value = false} ref={vir} items={store.nodeList} itemEstimatedSize={22} >
                            {{
                                default: (scope) => {
                                    return <Node data={scope.item} />;
                                }
                            }}
                        </VirtualList>
                    </div>
                </Dropdown>
                : <div class="cm-tree-empty">{emptyText}</div>
            : <div class="cm-tree">
                {
                    props.data && props.data.length
                        ? <VirtualList ref={vir} items={store.nodeList} itemEstimatedSize={22}>
                            {{
                                default: (scope) => {
                                    return <Node key={scope.item.id} data={scope.item} />;
                                }
                            }}
                        </VirtualList>
                        : <div class="cm-tree-empty">{emptyText}</div>
                }
            </div>;
    }
});
